﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace MidasGtsExporter.LSDYNA
{
	public class LsDynaExporter
	{
		#region private fields

		private SortedList<int, Node> _nodeList;

		private SortedList<int, Elem> _elem3DList;

		private SortedList<int, Elem> _elem2DList;
		//private SortedList<int, Elem> _elem1DList;

		private SortedList<int, List<int>> _nodeGroups;
		private SortedList<int, List<int>> _elem3DGroups;

		private SortedList<int, List<int>> _elem2DGroups;
		//private SortedList<int, List<int>> _elem1DGroups;

		private Dictionary<int, string> _nodeGroupNames;
		private Dictionary<int, string> _elem3DGroupNames;
		private Dictionary<int, string> _elem2DGroupNames;

		private readonly Dictionary<int, List<int>> _commonElem2DGroups = new Dictionary<int, List<int>>();
		private readonly Dictionary<int, List<int>> _segmentGroups = new Dictionary<int, List<int>>();

		private const string SegmentGroupNamePrefix = "eset_segment_";

		#endregion

		#region public properties

		public string InputFilePath { get; set; }
		public string OutputFolderPath { get; set; }
		public string OutputFileName { get; set; } = "LsDynaMesh"; // 不包含扩展名

		public LsDynaOption.Option Option { get; set; }

		public string OutputFileNameWithExtension => $"{OutputFileName}.k";

		public string IncludeFolderName => $"{OutputFileName}_Include";

		//public string IncludeFolderPath => Path.Combine(OutputFolderPath, IncludeFolderName);
		public string IncludeFolderPath => OutputFolderPath;

		public string OutputPartFileName => $"{OutputFileName}_Part.k";
		public string OutputNodeFileName => $"{OutputFileName}_Node.k";
		public string OutputElemFileName => $"{OutputFileName}_Elem.k";
		public string OutputNodeListFileName => $"{OutputFileName}_NodeList.k";
		public string OutputSegmentFileName => $"{OutputFileName}_Segment.k";

		#endregion

		/// <summary>
		/// Do Export work
		/// </summary>
		public void DoExport(BackgroundWorker backgroundWorker)
		{
			Debug.Assert(IO.IsFileExist(InputFilePath));

			//
			// Parse Midas GTS FPN file
			//
			backgroundWorker.ReportProgress(10, "解析FPN文件内容 ......");

			var gtsReader = new GtsFpnDataReader();
			gtsReader.ReadData(InputFilePath);

			_nodeList = gtsReader.NodeList;
			_elem3DList = gtsReader.Elem3DList;
			_elem2DList = gtsReader.Elem2DList;
			Debug.Assert(_nodeList != null && _elem3DList != null && _elem2DList != null);

			if (_nodeList.Count <= 0 || (_elem3DList.Count <= 0) && (_elem2DList.Count <= 0))
			{
				backgroundWorker.ReportProgress(100, "*** FPN文件中无有效节点和单元数据.");
				return;
			}

			var elemGroupNames = gtsReader.ElemGroupNames;
			var nodeGroupNames = gtsReader.NodeGroupNames;

			_elem3DGroups = gtsReader.Elem3DGroups;
			_elem2DGroups = gtsReader.Elem2DGroups;
			_nodeGroups = gtsReader.NodeGroups;

			_nodeGroupNames = gtsReader.NodeGroups
				.ToDictionary(kv => kv.Key, kv => $"nset_{nodeGroupNames[kv.Key]}");
			_elem3DGroupNames = _elem3DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");
			_elem2DGroupNames = _elem2DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");

			//
			// Check node and element data
			//
			if (Option.CheckInputData)
			{
				backgroundWorker.ReportProgress(50, "检查节点和单元数据 ......");
			}

			//
			// Output
			//
			backgroundWorker.ReportProgress(60, "输出LS-DYNA网格数据 ......");

			if (_elem2DGroups != null && _elem2DGroups.Count > 0)
			{
				Debug.Assert(_segmentGroups != null && _segmentGroups.Count == 0);
				foreach (var groupData in _elem2DGroups)
				{
					var groupId = groupData.Key;
					var groupName = _elem2DGroupNames[groupId];
					if (groupName.StartsWith(SegmentGroupNamePrefix,
						StringComparison.OrdinalIgnoreCase))
					{
						_segmentGroups.Add(groupId, _elem2DGroups[groupId]);
					}
					else
					{
						_commonElem2DGroups.Add(groupId, _elem2DGroups[groupId]);
					}
				}
			}

			var mainOutputFilePath = Path.Combine(OutputFolderPath, OutputFileNameWithExtension);
			if (!IO.IsFileExist(mainOutputFilePath) || Option.OverwriteMainOutputFile)
			{
				OutputMainContent();
			}

			OutputPartContent();
			OutputNodeContent();
			OutputElementContent();
			OutputNodeListContent();
			OutputSegmentContent();

			//
			// Output statistical information
			//
			{
				backgroundWorker.ReportProgress(90, " ****** 输出数据统计 ****** ");

				//
				var nodeCount = _nodeList.Count;
				var elem3DCount = _elem3DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elem2DCount = _elem2DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elemCount = elem3DCount + elem2DCount;

				var newLine = Environment.NewLine;
				backgroundWorker.ReportProgress(91, $"节点数量：{nodeCount}");
				if (_nodeGroups != null && _nodeGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(94,
						string.Join(newLine, _nodeGroups.Select(g => $"    {_nodeGroupNames[g.Key]}: {g.Value.Count}")));
				}

				backgroundWorker.ReportProgress(92, $"单元数量：{elemCount}");
				if (_elem3DGroups != null && _elem3DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(93, $"3D单元数量：{elem3DCount}");
					backgroundWorker.ReportProgress(94,
						string.Join(newLine, _elem3DGroups.Select(g => $"    {_elem3DGroupNames[g.Key]}: {g.Value.Count}")));
				}
				if (_elem2DGroups != null && _elem2DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(95, $"2D单元数量：{elem2DCount}");
					backgroundWorker.ReportProgress(96,
						string.Join(newLine, _elem2DGroups.Select(g => $"    {_elem2DGroupNames[g.Key]}: {g.Value.Count}")));
				}
			}
		}

		/// <summary>
		/// 创建Include目录，用户保存part, node, elem, nodeList, segment文件
		/// </summary>
		private void CreateIncludeFolder()
		{
			Debug.Assert(IO.IsFolderExist(OutputFolderPath));
			var includeFolder = Path.Combine(OutputFolderPath, IncludeFolderName);
			if (Directory.Exists(includeFolder))
			{
				var di = new DirectoryInfo(includeFolder);
				foreach (var fi in di.GetFiles())
				{
					fi.Delete();
				}
			}
			else
			{
				Directory.CreateDirectory(includeFolder);
			}
		}

		/// <summary>
		/// Output main mesh file
		/// </summary>
		private void OutputMainContent()
		{
			var content = new List<string>()
			{
				"$",
				$"$ LS-DYNA Mesh generated by MidasGtsExporter v{AppInfo.Version} on {DateTime.Now}",
				"$",
				"*KEYWORD",
				"*TITLE",
				"LS-DYNA Mesh",
			};

			//
			// control options
			//
			content.AddRange(new[]
			{
				"$",
				"$ ---------- Control option ---------------------------------------",
				"*CONTROL_SOLID",
				",,4,1",
				"",
				"*CONTROL_TERMINATION",
				"1.0,,,,1e+08",
			});

			//
			// database options
			//
			content.AddRange(new[]
			{
				"$",
				"$ ---------- Database option ---------------------------------------",
				"*DATABASE_BINARY_D3PLOT",
				"0.5",
				"*DATABASE_BINARY_D3THDT",
				"0.1",
			});

			//
			// mat definition
			//
			content.AddRange(new[]
			{
				"$",
				"$ ---------- Material Definition ---------------------------------------",
				"*MAT_ELASTIC_TITLE",
				"mat01",
				"1,10.0",
			});

			//
			// section definition
			//
			if (_elem3DGroups != null && _elem3DGroups.Count > 0)
			{
				content.AddRange(new[]
				{
					"$ ---------- Sections Definition ---------------------------------------",
					"*SECTION_SOLID_TITLE",
					"section01",
					"1,1,0",
				});
			}
			if (_commonElem2DGroups != null && _commonElem2DGroups.Count > 0)
			{
				content.AddRange(new[]
				{
					"*SECTION_SHELL_TITLE",
					"section02",
					"2,2,1,2,1,,,1",
					"1,1,1,1",
				});
			}

			//
			// part definition
			//
			content.AddRange(new[]
			{
				"$",
				"$ ---------- Part Definition ---------------------------------------",
				"*INCLUDE",
				$"{OutputPartFileName}",
			});

			//
			content.AddRange(new[]
			{
				"$",
				"*END",
			});

			//
			// Write to file
			//
			var filePath = Path.Combine(OutputFolderPath, OutputFileNameWithExtension);
			File.WriteAllLines(filePath, content);
		}

		/// <summary>
		/// Output part definition
		/// </summary>
		private void OutputPartContent()
		{
			var content = new List<string>()
			{
				"$",
				$"$ LS-DYNA Mesh generated by MidasGtsExporter v{AppInfo.Version} on {DateTime.Now}",
				"$",
				"*KEYWORD",
				"$ ---------- Part Definition ---------------------------------------",
			};

			//
			// part definition
			//
			// 3D element part
			if (_elem3DGroups != null && _elem3DGroups.Count > 0)
			{
				var elemGroupData = _elem3DGroups;
				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var groupName = _elem3DGroupNames[groupId];
					var partId = groupId;
					content.AddRange(new[]
					{
						"*PART",
						$"{groupName}",
						$"{partId},1,1"
					});
				}
			}

			// 2D element part
			if (_commonElem2DGroups != null && _commonElem2DGroups.Count > 0)
			{
				var elemGroupData = _commonElem2DGroups;
				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var groupName = _elem2DGroupNames[groupId];
					var partId = groupId;
					content.AddRange(new[]
					{
						"*PART",
						$"{groupName}",
						$"{partId},2,1"
					});
				}
			}

			//
			// Output node include keyword
			//
			content.AddRange(new[]
			{
				"$",
				"$",
				"$ ---------- Node Definition ---------------------------------------",
				$"$ NodeCount: {_nodeList.Count}",
				"*INCLUDE",
				$"{OutputNodeFileName}",
			});

			//
			// Output element include keyword
			//
			content.AddRange(new[]
			{
				"$",
				"$ ---------- Element Definition ------------------------------------",
			});

			// 3D element groups
			if (_elem3DGroups != null && _elem3DGroups.Count > 0)
			{
				var elemGroupData = _elem3DGroups;
				var groupNames = _elem3DGroupNames;

				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var partId = groupId;
					var groupName = groupNames[groupId];
					var elemCount = elemGroupData[groupId].Count;
					content.Add($"$ ELEMENT_SOLID, PartID: {partId}, PartName: {groupName}, ElemCount: {elemCount}");
				}
			}

			// 2D element groups
			if (_commonElem2DGroups != null && _commonElem2DGroups.Count > 0)
			{
				var elemGroupData = _commonElem2DGroups;
				var groupNames = _elem2DGroupNames;

				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var partId = groupId;
					var groupName = groupNames[groupId];
					var elemCount = elemGroupData[groupId].Count;
					content.Add($"$ ELEMENT_SHELL, PartID: {partId}, PartName: {groupName}, ElemCount: {elemCount}");
				}
			}

			content.AddRange(new[]
			{
				"*INCLUDE",
				$"{OutputElemFileName}",
			});

			//
			// Output node list include keyword
			//
			if (_nodeGroups != null && _nodeGroups.Count > 0)
			{
				content.AddRange(new[]
				{
					"$",
					"$ ---------- NodeList Definition ---------------------------------",
				});

				var groupIds = _nodeGroups.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var setId = groupId;
					var groupName = _nodeGroupNames[groupId];
					var nodeCount = _nodeGroups[groupId].Count;
					content.AddRange(new[]
					{
						$"$ NodeListID: {setId}, SetName: {groupName}, NodeCount: {nodeCount}",
					});
				}

				content.AddRange(new[]
				{
					"*INCLUDE",
					$"{OutputNodeListFileName}",
				});
			}

			//
			// Output segment include keyword
			//
			if (_segmentGroups != null && _segmentGroups.Count > 0)
			{
				content.AddRange(new[]
				{
					"$",
					"$ ---------- Segment Definition ----------------------------------",
				});

				var elemGroupData = _segmentGroups;
				var groupNames = _elem2DGroupNames;

				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var segmentId = groupId;
					var groupName = groupNames[groupId];
					var elemCount = elemGroupData[groupId].Count;
					content.AddRange(new[]
					{
						$"$ SegmentID: {segmentId}, SetName: {groupName}, ElemCount: {elemCount}",
					});
				}

				content.AddRange(new[]
				{
					"*INCLUDE",
					$"{OutputSegmentFileName}",
				});
			}

			//
			content.AddRange(new[]
			{
				"$",
				"*END",
			});

			//
			// Write to file
			//
			var filePath = Path.Combine(IncludeFolderPath, OutputPartFileName);
			File.WriteAllLines(filePath, content);
		}

		/// <summary>
		/// Output node data
		/// </summary>
		private void OutputNodeContent()
		{
			var content = new List<string>()
			{
				"$",
				$"$ Node data for {OutputFileNameWithExtension}",
				"$",
				"*KEYWORD",
				"*NODE",
			};

			content.AddRange(_nodeList.Select(kv =>
			{
				var node = kv.Value;
				return $"{node.Id},{node.X:E6},{node.Y:E6},{node.Z:E6}";
			}));

			content.Add("*END");

			//
			// Write to file
			//
			var filePath = Path.Combine(IncludeFolderPath, OutputNodeFileName);
			File.WriteAllLines(filePath, content);
		}

		/// <summary>
		/// Output element data
		/// </summary>
		private void OutputElementContent()
		{
			Debug.Assert((_elem3DGroups != null && _elem3DGroups.Count > 0)
			             || (_commonElem2DGroups != null && _commonElem2DGroups.Count > 0));

			var content = new List<string>()
			{
				"$",
				$"$ Element data for {OutputFileNameWithExtension}",
				"$",
				"*KEYWORD",
			};

			//
			// 3D element groups
			//
			if (_elem3DGroups != null && _elem3DGroups.Count > 0)
			{
				var elemGroupData = _elem3DGroups;

				content.Add("*ELEMENT_SOLID");
				var groupIds = elemGroupData.Keys;
				foreach (var groupId in groupIds)
				{
					var partId = groupId;
					var elemData = elemGroupData[groupId];

					//
					// HEXA
					//
					content.AddRange(new[]
					{
						"$",
						"$ HEXA",
					});
					var sourceNodeCount = 8;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem3DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem3DList[elemId];
							var n1 = elem.NodeIds[1 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[3 - 1];
							var n4 = elem.NodeIds[4 - 1];
							var n5 = elem.NodeIds[5 - 1];
							var n6 = elem.NodeIds[6 - 1];
							var n7 = elem.NodeIds[7 - 1];
							var n8 = elem.NodeIds[8 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n4},{n5},{n6},{n7},{n8}";
						}));

					//
					// PRISM
					//
					content.AddRange(new[]
					{
						"$",
						"$ PRISM",
					});
					sourceNodeCount = 6;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem3DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem3DList[elemId];
							var n1 = elem.NodeIds[3 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[5 - 1];
							var n4 = elem.NodeIds[6 - 1];
							var n5 = elem.NodeIds[1 - 1];
							var n6 = elem.NodeIds[4 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n4},{n5},{n5},{n6},{n6}";
						}));

					//
					// PYRAM
					//
					content.AddRange(new[]
					{
						"$",
						"$ PYRAM",
					});
					sourceNodeCount = 5;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem3DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem3DList[elemId];
							var n1 = elem.NodeIds[1 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[3 - 1];
							var n4 = elem.NodeIds[4 - 1];
							var n5 = elem.NodeIds[5 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n4},{n5},{n5},{n5},{n5}";
						}));

					//
					// TETRA
					//
					content.AddRange(new[]
					{
						"$",
						"$ TETRA",
					});
					sourceNodeCount = 4;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem3DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem3DList[elemId];
							var n1 = elem.NodeIds[1 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[3 - 1];
							var n4 = elem.NodeIds[4 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n4},{n4},{n4},{n4},{n4}";
						}));

					content.AddRange(new[]
					{
						"$",
						"$",
					});
				}
			}

			//
			// 2D element groups
			//
			if (_commonElem2DGroups != null && _commonElem2DGroups.Count > 0)
			{
				var elemGroupData = _commonElem2DGroups;

				content.Add("*ELEMENT_SHELL");
				var groupIds = elemGroupData.Keys.OrderBy(id => id);
				foreach (var groupId in groupIds)
				{
					var partId = groupId;
					var elemData = elemGroupData[groupId];

					//
					// RECT
					//
					content.AddRange(new[]
					{
						"$",
						"$ RECT",
					});
					var sourceNodeCount = 4;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem2DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem2DList[elemId];
							var n1 = elem.NodeIds[1 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[3 - 1];
							var n4 = elem.NodeIds[4 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n4}";
						}));

					//
					// TRIA
					//
					content.AddRange(new[]
					{
						"$",
						"$ TRIA",
					});
					sourceNodeCount = 3;
					content.AddRange(elemData
						.Where(elemId =>
						{
							var elem = _elem2DList[elemId];
							return elem.NodeIds.Count == sourceNodeCount;
						})
						.Select(elemId =>
						{
							var elem = _elem2DList[elemId];
							var n1 = elem.NodeIds[1 - 1];
							var n2 = elem.NodeIds[2 - 1];
							var n3 = elem.NodeIds[3 - 1];
							return $"{elem.Id},{partId},{n1},{n2},{n3},{n3}";
						}));

					content.AddRange(new[]
					{
						"$",
						"$",
					});
				}
			}

			content.Add("*END");

			//
			// Write to file
			//
			var filePath = Path.Combine(IncludeFolderPath, OutputElemFileName);
			File.WriteAllLines(filePath, content);
		}

		/// <summary>
		/// Output node group data
		/// </summary>
		private void OutputNodeListContent()
		{
			if (_nodeGroups == null || _nodeGroups.Count <= 0)
				return;

			var content = new List<string>()
			{
				"$",
				$"$ NodeList data for {OutputFileNameWithExtension}",
				"$",
				"*KEYWORD",
			};

			var groupIds = _nodeGroups.Keys.OrderBy(id => id);
			foreach (var groupId in groupIds)
			{
				var groupName = _nodeGroupNames[groupId];
				var setId = groupId;
				content.AddRange(new[]
				{
					"*SET_NODE_LIST_TITLE",
					$"{groupName}",
					$"{setId},,,,,MECH"
				});

				var nodeIds = _nodeGroups[groupId];
				const int idCountPerLine = 8;
				content.AddRange(nodeIds
					.GetSlices(idCountPerLine)
					.Select(ids => string.Join(",", ids.Select(id => $"{id}"))));

				content.AddRange(new[]
				{
					"$",
					"$",
				});
			}

			content.Add("*END");

			//
			// Write to file
			//
			var filePath = Path.Combine(IncludeFolderPath, OutputNodeListFileName);
			File.WriteAllLines(filePath, content);
		}

		/// <summary>
		/// Output segment data
		/// </summary>
		private void OutputSegmentContent()
		{
			if (_segmentGroups == null || _segmentGroups.Count <= 0)
				return;

			var content = new List<string>()
			{
				"$",
				$"$ Segment data for {OutputFileNameWithExtension}",
				"$",
				"*KEYWORD",
			};

			var elemGroupData = _segmentGroups;
			var groupIds = elemGroupData.Keys.OrderBy(id => id);
			foreach (var groupId in groupIds)
			{
				var groupName = _elem2DGroupNames[groupId];
				var segmentId = groupId;
				content.AddRange(new[]
				{
					"*SET_SEGMENT_TITLE",
					$"{groupName}",
					$"{segmentId},,,,,MECH",
				});

				var elemData = elemGroupData[groupId];
				foreach (var elemId in elemData)
				{
					var elem = _elem2DList[elemId];
					var nodeCount = elem.NodeIds.Count;
					Debug.Assert(3 <= nodeCount && nodeCount <= 4);
					if (nodeCount == 4)
					{
						content.Add(string.Join(",",
							elem.NodeIds.Select(id => $"{id}")));
					}
					else
					{
						content.Add(string.Join(",", elem.NodeIds.Select(id => $"{id}"))
						            + $",{elem.NodeIds.Last()}");
					}
				}

				content.AddRange(new[]
				{
					"$",
					"$",
				});
			}

			content.Add("*END");

			//
			// Write to file
			//
			var filePath = Path.Combine(IncludeFolderPath, OutputSegmentFileName);
			File.WriteAllLines(filePath, content);
		}
	}
}